import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import Interfaces.IAgentDownHandler;
import Interfaces.IAgentUpHandler;
import Interfaces.IManagmentChannelListener;
import Messages.DiscoveryMessage;


public class ManagmentChannelListener extends Thread implements IManagmentChannelListener
{
	private boolean m_isListening;
	private ConcurrentHashMap<String,Tuple<Date,DiscoveryMessage>> m_agentsByHost;
	private ArrayList<IAgentUpHandler> m_agentUpHandlers;
	private ArrayList<IAgentDownHandler> m_agentDownHandlers;
	private MulticastSocket m_socket;
	private Timer m_discoveryTimer;
	
	public ManagmentChannelListener(String IP, int port) throws UnknownHostException, IOException
	{
		m_isListening = false;
		m_agentUpHandlers = new ArrayList<IAgentUpHandler>();
		m_agentDownHandlers = new ArrayList<IAgentDownHandler>();
		m_agentsByHost = new ConcurrentHashMap<String, Tuple<Date,DiscoveryMessage>>();
		m_socket = new MulticastSocket(port);
		m_socket.joinGroup(InetAddress.getByName(IP));
		m_discoveryTimer = new Timer();
	}

	public void AddAgentUpHandler(IAgentUpHandler handler) 
	{
		m_agentUpHandlers.add(handler);
	}
	
	public void AddAgentDownHandler(IAgentDownHandler handler)
	{
		m_agentDownHandlers.add(handler);
	}
	
	private void NotifyAgentUp(DiscoveryMessage message)
	{
		for(IAgentUpHandler handler: m_agentUpHandlers)
		{
			handler.HandleAgentUp(message);
		}
	}
	
	private void NotifyAgentDown(String host)
	{
		for(IAgentDownHandler handler: m_agentDownHandlers)
		{
			handler.HandleAgentDown(host);
		}
	}

	public Collection<DiscoveryMessage> GetCurrentAgents() 
	{
		Collection<DiscoveryMessage> result = new ArrayList<DiscoveryMessage>();
		Enumeration<Tuple<Date,DiscoveryMessage>> elements = m_agentsByHost.elements();
		while(elements.hasMoreElements())
		{
			Tuple<Date,DiscoveryMessage> item = elements.nextElement();
			result.add(item.getItem2());
		}
		
		return result;
	}
	
	@Override
	public void run()
	{
		System.out.println("Start Mamagment channel listener thread");
		while(m_isListening)
		{
			try
			{
				byte[] recvBuf = new byte[5000];
				DatagramPacket packet = new DatagramPacket(recvBuf,
		                                                 recvBuf.length);
				m_socket.receive(packet);
				ByteArrayInputStream byteStream = new ByteArrayInputStream(recvBuf);
				ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(byteStream));
		      
				DiscoveryMessage message =  (DiscoveryMessage)is.readObject();
				
				if(!m_agentsByHost.containsKey(message.AgentHost))
				{
					System.out.println("Got first discovery message");
					NotifyAgentUp(message);
				}
				
				// update the last time we got discovery message
				Tuple<Date,DiscoveryMessage> tuple = 
						new Tuple<Date, DiscoveryMessage>(
								Calendar.getInstance().getTime(), message);
				m_agentsByHost.put(message.AgentHost, tuple);
				
				is.close();
		      }
			  catch(IOException e)
			  {
				  e.printStackTrace();  
			  }
		      catch (ClassNotFoundException e) 
		      {
				e.printStackTrace();
		      }
		}
	}
	
	private void startTimer()
	{
		m_discoveryTimer.scheduleAtFixedRate(new TimerTask() {
			  @Override
			  public void run() 
			  {
				  List<String> agentsToRemove = new ArrayList<String>(); 
				  
				  Enumeration<Tuple<Date,DiscoveryMessage>> elements = m_agentsByHost.elements();
				  while(elements.hasMoreElements())
				  {
					  Tuple<Date,DiscoveryMessage> agent = elements.nextElement();
					  if((Calendar.getInstance().getTime().getTime() - 
						  agent.getItem1().getTime()) > 6000)
					  {
						  // Add to agents to remove list
						  agentsToRemove.add(agent.getItem2().AgentHost);
					  }
				  }
				  
				  for(String host:agentsToRemove)
				  {
					  NotifyAgentDown(host);
					  m_agentsByHost.remove(host);
				  }
			  }
			}, 0, 3000);
	}
	
	private void stopTimer()
	{
		m_discoveryTimer.cancel();
	}

	public void startListening()
	{
		m_isListening = true;
		start();
		startTimer();
	}
	
	public void stopListening()
	{
		m_isListening = false;
		startTimer();
	}

}
 